package com.nulldev.util.net.dsap.packets;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.util.UUID;

import com.nulldev.util.lang.json.Json;
import com.nulldev.util.lang.json.JsonObject;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.math.MathUtil;
import com.nulldev.util.net.IPUtil;
import com.nulldev.util.net.asyncSockets.ASyncSocket;
import com.nulldev.util.net.dsap.Client;
import com.nulldev.util.net.dsap.DSAPRequestServerType;
import com.nulldev.util.net.dsap.Server;
import com.nulldev.util.net.experimental.udp2.Connection;
import com.nulldev.util.net.experimental.udp2.DataReceivedListener;
import com.nulldev.util.net.experimental.udp2.Message;
import com.nulldev.util.net.packets.APILevel;
import com.nulldev.util.net.packets.NetInfo;
import com.nulldev.util.net.packets.NetInfoAdapter;
import com.nulldev.util.net.packets.NetPacket;
import com.nulldev.util.net.packets.PacketManager;
import com.nulldev.util.net.packets.UniversalPacket;
import com.nulldev.util.net.tcp.TCPCore;
import com.nulldev.util.net.tcp.server.TCPServer;
import com.nulldev.util.net.udp.UDPServer;

@APILevel(level = 4)
public class DSAPRequestSocket extends UniversalPacket {
	
	private static final Logger log = LoggerFactory.getLoggerName("DSAP", true, true);

	public DSAPRequestSocket() {
		super("Dynamic Socket Allocation Protocol: Request Socket", "dsap.request");
	}

	@Override
	public void client(byte[] data, byte[] raw, NetInfo client) {
		final JsonObject meta = Json.parse(new String(data)).asObject();
		if (client.get() instanceof Client) {
			((Client)client.get()).onDSAP(meta);
		}
	}

	@Override
	public void server(byte[] data, byte[] raw, NetInfo connection) {
		final int i = MathUtil.safeParseI(new String(data), 1);
		final JsonObject obj = Json.object();
		if (connection.get() instanceof Server) {
			if (((Server)connection.get()).isDSAPready()) {
				final InetAddress addr = IPUtil.getLocalHostLANAddress_safeinet();
				final int freePort = IPUtil.nextFreePort();
				obj.add("dsapMSG", "OK");
				obj.add("ip", addr.getHostAddress());
				obj.add("port", freePort);
				final DSAPRequestServerType m = DSAPRequestServerType.get((short) i);
				obj.add("mode", m.sname());
				obj.add("enc", ((Server)connection.get()).supportsDSAP_SecureStreams());
				if (m == DSAPRequestServerType.TCP) {
					final TCPServer tcps = new TCPServer("DSAP: Allocated Server", "dsap.tcp", freePort, false) {
						
						@Override
						public void receive(TCPCore connection, String event, String payload, byte[] bytes, long length, final long readTime) {
							final NetPacket packet = PacketManager.parsePacket(this, connection, event, bytes);
							if (!packet.isGeneric()) {
								packet.server(packet.getData(), event, NetInfoAdapter.create(connection));
							}
						}
						
						@Override
						public boolean isDSAPSpawn() {
							return true;
						}
						
						@Override
						public void onServerConnection(final ServerConnection conn) {
							if (this.supportsDSAP_SecureStreams()) {
								conn.annouceServerKeys();
							}
						}
					};
					tcps.start();
					log.info("Allocated a TCP server for " + connection.format() + ".");
				}else if (m == DSAPRequestServerType.UDP) {
					try {
						final UDPServer udps = new UDPServer(addr, freePort, 64, 64000) {
							
							@Override
							public boolean isDSAPSpawn() {
								return true;
							}
							
							@Override
							public void closeDSAP() {
								try {
									this.close();
								} catch (IOException e) {
								}
							}
							
							@Override
							public void ext_handle(DatagramPacket p, String data) {
								final NetPacket packet = PacketManager.parsePacket(this, null, data);
								if (!packet.isGeneric()) {
									packet.server(packet.getData(), data, NetInfoAdapter.create(this));
								}
							}
						};
						if (!udps.supportsSDTP()) {
							obj.add("error", "DSAP_MODE_NOT_SUPPORTED");
							connection.reply(PacketManager.fromID("dsap.request", obj.toString()));
							return;
						}
						udps.useFastMode = true;
						udps.start();
						log.info("Allocated a UDP server for " + connection.format() + ".");
					}catch (Exception e) {
						obj.add("error", "DSAP_SOCKET_FAILED");
						connection.reply(PacketManager.fromID("dsap.request", obj.toString()));
						return;					
					}
				}else if (m == DSAPRequestServerType.RUDP) {
					final com.nulldev.util.net.experimental.udp2.Server s = 
							new com.nulldev.util.net.experimental.udp2.Server(freePort, addr.getHostName(), 128) {
						@Override
						public boolean isDSAPSpawn() {
							return true;
						}
					};
					s.registerServerListerner(new DataReceivedListener() {
						
						@Override
						public void receivedMessage(Message message, Connection conn) {
							final byte[] msg = message.read();
							final NetPacket packet = PacketManager.parsePacket(this, s, new String(msg));
							if (!packet.isGeneric()) {
								packet.server(packet.getData(), msg, NetInfoAdapter.create(conn));
							}
						}
						
						@Override
						public void disconnected(UUID connectionId) {
						}
						
						@Override
						public void connected(Connection connection) {
						}
					});
					s.startServer();
					log.info("Allocated a rUDP server for " + connection.format() + ".");
				}
				connection.reply(PacketManager.fromID("dsap.request", obj.toString()));
			}else {
				obj.add("error", "DSAP_NOT_SUPPORTED");
				connection.reply(PacketManager.fromID("dsap.request", obj.toString()));
			}
		}else {
			obj.add("error", "DSAP_NOT_SERVER");
			connection.reply(PacketManager.fromID("dsap.request", obj.toString()));
		}
	}

}
